#include "EventWrapper.h"
#include "EventException.h"
#include "KeyEvent.h"
#include "TextInputEvent.h"
#include "QuitEvent.h"
#include "AddBlockToPieceNetworkEvent.h"
#include "ClientDisconnectedNetworkEvent.h"
#include "ClientConnectNetworkEvent.h"
#include "ClientReadyToPlayNetworkEvent.h"
#include "MovePieceNetworkEvent.h"
#include "GameStartNetworkEvent.h"
#include "GameOverNetworkEvent.h"
#include "AddPieceNetworkEvent.h"
#include "UpdatePieceNetworkEvent.h"
#include "RemovePieceNetworkEvent.h"
#include "AddBlockToRowNetworkEvent.h"
#include "UpdateBlockNetworkEvent.h"
#include "AddRowNetworkEvent.h"
#include "MoveRowNetworkEvent.h"
#include "RemoveRowNetworkEvent.h"
#include "AddScorePlayerNetworkEvent.h"
#include "UpdateScoreNetworkEvent.h"
#include "UpdatePlayerNetworkEvent.h"
#include "UpdatePreviewNetworkEvent.h"
#include <sstream>
#include <cstdlib>

EventWrapper::EventWrapper()
{
}

EventWrapper::~EventWrapper()
{
}

void EventWrapper::push(void* header, void* data)
{
    SDL_Event event;
    event.type = SDL_USEREVENT;
    event.user.code = NETWORK;
    event.user.data1 = header;
    event.user.data2 = data;
    SDL_PushEvent(&event);
}

void EventWrapper::push(Event* internalEvent)
{
    SDL_Event event;
    event.type = SDL_USEREVENT;
    event.user.code = INTERNAL;
    event.user.data1 = internalEvent;
    SDL_PushEvent(&event);
}

void EventWrapper::pushQuit()
{
    SDL_Event event;
    event.type = SDL_QUIT;
    SDL_PushEvent(&event);
}

Events EventWrapper::getEvents()
{
    Events result;
    SDL_Event event;
    while (SDL_PollEvent(&event))
    {
        result.push_back(getNextEvent(event));
    }
    return result;
}

Event* EventWrapper::createUserEvent(const SDL_Event& event)
{
    if (event.user.code == NETWORK)
    {
        Event* userEvent = createNetworkUserEvent(event);
        clear(event);
        return userEvent;
    }
    else
    {
        return (Event*)event.user.data1;
    }
}

void EventWrapper::clear(const SDL_Event& event)
{
    free(event.user.data1);
    free(event.user.data2);
}

Event* EventWrapper::getNextEvent(const SDL_Event& event)
{
    switch (event.type)
    {
    case SDL_TEXTINPUT:
        return new TextInputEvent(event.text.text[0]);

    case SDL_KEYDOWN:
        return new KeyEvent(event.key.keysym.sym);

    case SDL_USEREVENT:
    {
        return createUserEvent(event);
    }
    case SDL_QUIT:
        return new QuitEvent();

    default:
        return new Event();
    }
}

Event* EventWrapper::throwEventError(int code)
{
    std::ostringstream errorString;
    errorString << "Event with ENUM code " << code << " not handled";
    throw EventException(errorString.str());
    return new Event();
}

Event* EventWrapper::createNetworkUserEvent(const SDL_Event& event)
{
    NetworkEvent::Header header = *((NetworkEvent::Header*)event.user.data1);
    switch (header.code)
    {
    case NetworkEvent::CLIENT_DISCONNECTED:
        return new ClientDisconnectedNetworkEvent(header);
    case NetworkEvent::CLIENT_CONNECTED:
        return new ClientConnectNetworkEvent(header, *((ClientConnectNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_PLAYER_UPDATE:
        return new UpdatePlayerNetworkEvent(header, *((UpdatePlayerNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::CLIENT_READY_TO_PLAY:
        return new ClientReadyToPlayNetworkEvent(header);
    case NetworkEvent::CLIENT_MOVE_PIECE:
        return new MovePieceNetworkEvent(header, *((MovePieceNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_GAME_START:
        return new GameStartNetworkEvent(header, *((GameStartNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_GAME_OVER:
        return new GameOverNetworkEvent(header, *((GameOverNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_ADD_PIECE:
        return new AddPieceNetworkEvent(header, *((AddPieceNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_UPDATE_PIECE:
        return new UpdatePieceNetworkEvent(header, *((UpdatePieceNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_REMOVE_PIECE:
        return new RemovePieceNetworkEvent(header, *((RemovePieceNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_ADD_BLOCK_TO_ROW:
        return new AddBlockToRowNetworkEvent(header, *((AddBlockToRowNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_ADD_BLOCK_TO_PIECE:
        return new AddBlockToPieceNetworkEvent(header, *((AddBlockToPieceNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_UPDATE_BLOCK:
        return new UpdateBlockNetworkEvent(header, *((UpdateBlockNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_ADD_ROW:
        return new AddRowNetworkEvent(header, *((AddRowNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_MOVE_ROW:
        return new MoveRowNetworkEvent(header, *((MoveRowNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_REMOVE_ROW:
        return new RemoveRowNetworkEvent(header, *((RemoveRowNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_ADD_SCORE_PLAYER:
        return new AddScorePlayerNetworkEvent(header, *((AddScorePlayerNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_UPDATE_SCORE:
        return new UpdateScoreNetworkEvent(header, *((UpdateScoreNetworkEvent::Data*)event.user.data2));
    case NetworkEvent::SERVER_UPDATE_PREVIEW:
            return new UpdatePreviewNetworkEvent(header, *((UpdatePreviewNetworkEvent::Data*)event.user.data2));

    default:
        return throwEventError(header.code);
    }
}
